﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Pika.Collections
{
    /// <summary>
    /// Représente la structure de données matrice
    /// </summary>
    public class Matrix : ICollection
    {
        #region Champs
        protected Object[,] _tab;
        protected Size _taille;
        protected int _version;
        [NonSerialized]
        private Object _syncRoot;
        static readonly Object[,] _emptyMatrix = new Object[0, 0];
        #endregion

        #region Méthodes
        /// <summary>
        /// Construit la matrice
        /// </summary>
        public Matrix()
        {
            _tab = _emptyMatrix;
            this.Size = new Size(0, 0);
            _version = 0;
        }
        /// <summary>
        /// Construit la matrice 
        /// </summary>
        /// <param name="columns"></param>
        /// <param name="rows"></param>
        public Matrix(int columns, int rows)
            : this(new Size(columns, rows))
        {
        }
        /// <summary>
        /// Construit la matrice avec la taille définit
        /// </summary>
        /// <param name="taille"></param>
        public  Matrix(Size taille)
        {
            if(taille == null)
            {
                throw new ArgumentNullException("taille");
            }
            this.Size = taille;
            _tab = new Object[_taille.Rows, _taille.Columns];
            _version = 0;
        }
        public virtual void Clear()
        {
            if(_taille.Rows > 0 && _taille.Columns > 0)
            {
                Array.Clear(_tab, 0, _taille.Rows);
            }
            _version++;
        }
        public virtual void CopyTo(Array array)
        {
            this.CopyTo(array, 0);
        }
        public virtual void CopyTo(Array array, int arrayIndex)
        {
            if((array != null) && (array.Rank != 1))
            {
                throw new ArgumentException();
            }
            Contract.EndContractBlock();

            Array.Copy(_tab, 0, array, arrayIndex, _taille.Rows);
        }
        public virtual IEnumerator GetEnumerator()
        {
            Contract.Ensures(Contract.Result<IEnumerator>() != null);
            return new MatrixEnumeratorSimple(this);
        }
        #endregion

        #region Propriétés
        /// <summary>
        /// Retourne le nombre d'éléments de la matrice
        /// </summary>
        public virtual int Count
        {
            get
            {
                Contract.Ensures(Contract.Result<int>() >= 0);
                return _taille.Rows * _taille.Columns;
            }
        }
        /// 
        /// </summary>
        public virtual bool IsSynchronized
        {
            get { return false; }
        }
        /// <summary>
        /// Retourne la taille de la matrice
        /// </summary>
        public virtual Size Size
        {
            get
            {
                return _taille;
            }
            protected set
            {
                _taille = value;
            }
        }
        /// <summary>
        /// Retourne un objet qui peut être utilisé pour synchroniser l'accès à la matrice
        /// </summary>
        public virtual Object SyncRoot
        {
            get
            {
                if(_syncRoot == null)
                {
                    System.Threading.Interlocked.CompareExchange<Object>(ref _syncRoot, new Object(), null);
                }
                return _syncRoot;
            }
        }

        /// <summary>
        /// Retourne l'élement identifié par la ligne et la colonne
        /// </summary>
        /// <param name="row"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        public virtual Object this[int row, int column]
        {
            get
            {
                if(row < 0 || row >= _taille.Rows)
                {
                    throw new ArgumentOutOfRangeException("row");
                }
                if(column < 0 || column >= _taille.Columns)
                {
                    throw new ArgumentOutOfRangeException("column");
                }
                Contract.EndContractBlock();
                return _tab[row, column];
            }
            set
            {
                if(row < 0 || row >= _taille.Rows)
                {
                    throw new ArgumentOutOfRangeException("row");
                }
                if(column < 0 || column >= _taille.Columns)
                {
                    throw new ArgumentOutOfRangeException("column");
                }
                Contract.EndContractBlock();
                _tab[row, column] = value;
                _version++;
            }
        }
        #endregion

        #region Types imbriqués
        private sealed class MatrixEnumeratorSimple : IEnumerator
        {
            #region Champs
            private Matrix _matrice;
            private int _colonne, _ligne;
            private int _version;
            private bool _isMatrix;
            private Object _actuel;

            private static Object s_dummyObject = new Object();
            #endregion

            #region Méthodes
            internal MatrixEnumeratorSimple(Matrix matrice)
            {
                _matrice = matrice;
                _colonne = _ligne = 0;
                _version = matrice._version;
                _isMatrix = (matrice.GetType() == typeof(Matrix));
                _actuel = s_dummyObject; 
            }

            public bool MoveNext()
            {
                bool bresult;

                if(_version != _matrice._version)
                {
                    throw new InvalidOperationException();
                }

                bresult = false;
                if(_isMatrix == true)
                {
                    if(_ligne < _matrice.Size.Rows)
                    {
                        if(_colonne < (_matrice.Size.Columns - 1))
                        {
                            _actuel = _matrice[_ligne, _colonne++];
                            bresult = true;
                        }
                        else if(_colonne == (_matrice.Size.Columns -1))
                        {
                            _actuel = _matrice[_ligne, _colonne];
                            bresult = true;

                            _colonne = 0;
                            _ligne++;
                        }
                        else
                        {
                            _actuel = s_dummyObject;
                            _ligne = _matrice.Size.Rows;
                            _colonne = _matrice.Size.Columns;
                            bresult = false;
                        }
                    }
                    else
                    {
                        _actuel = s_dummyObject;
                        _ligne = _matrice.Size.Rows;
                        _colonne = _matrice.Size.Columns;
                        bresult = false;
                    }
                }
                return bresult;
            }
            public void Reset()
            {
                if(_version != _matrice._version)
                {
                    throw new InvalidOperationException("matrix");
                }
                _actuel = s_dummyObject;
                _colonne = _ligne = 0;
            }
            #endregion

            #region Propriétés
            public Object Current
            {
                get
                {
                    object temp = _actuel;
                    if(s_dummyObject == temp)
                    {
                        throw new InvalidOperationException("current");
                    }
                    return temp;
                }
            }
            #endregion
        }
        #endregion
    }
}
